// Copyright(c) 2008 Tri Tech Information Systems Inc. 
// Distributed under the Boost Software License, Version 1.0.
//     (See accompanying file ../../LICENSE_1_0.txt or copy at
//           http://www.boost.org/LICENSE_1_0.txt)
//     

#ifndef INCLUDE_PYTHON_EXCEPTION_H
#define INCLUDE_PYTHON_EXCEPTION_H
#include <boost/python.hpp>
#include <pyxx/python_error.h>
#include <stdexcept>
#include <string>

template<typename _ExceptionType, typename _BaseType>
struct exception_detail
{
    typedef boost::python::class_<
            _ExceptionType, 
            boost::python::bases<_BaseType> > return_type;    

    static boost::python::object pybase()
    {
        using namespace boost::python::converter;
        using namespace boost::python;
        registration const * p = registry::query( type_id<_BaseType>() );
        if(!p)
            throw std::logic_error( std::string(type_id<_BaseType>().name()) + " has not been wrapped");
        return object(handle<>(borrowed(p->get_class_object()))).attr("py_err_class");
    }
};

template<typename _ExceptionType>
struct exception_detail<_ExceptionType, boost::python::detail::not_specified>
{
    typedef boost::python::class_<_ExceptionType> return_type;    
    static boost::python::object pybase()
    {
        using namespace boost::python;
        return import("__builtin__").attr("Exception");
    }
};

template< typename ExceptionName >
struct PythonException : public IPythonError, public ExceptionName
{
    boost::python::object m_value;
    boost::python::object m_type;
    boost::python::object m_traceback;
    std::string m_msg;

    PythonException(const ExceptionName & exc, const std::string & msg, 
            boost::python::object type, boost::python::object val, boost::python::object tb) :
        ExceptionName(exc),
        m_value(val),
        m_type(type),
        m_traceback(tb),
        m_msg(msg)
    {
    }

    ~PythonException() throw()
    {
    }

    virtual const char * what() const throw()
    {
        return m_msg.c_str();
    }

    virtual void restore()
    {
        
        boost::python::incref(m_type.ptr());
        boost::python::incref(m_value.ptr());
        boost::python::incref(m_traceback.ptr());
        PyErr_Restore( m_type.ptr(), m_value.ptr(), m_traceback.ptr() );

    }
};

template<typename ExceptionType>
struct exception_thrower
{
    static void do_throw(boost::python::object obj, const std::string & msg, boost::python::object a, boost::python::object b, boost::python::object c)
    {
        ExceptionType exc = boost::python::extract<ExceptionType>(obj);
        throw PythonException<ExceptionType>(exc, msg, a, b, c);
    }
};

template<typename ExceptionType>
struct exception_to_cpp
{
    typedef exception_to_cpp<ExceptionType> register_type;
    exception_to_cpp()
    {
        using namespace boost::python;
        converter::registry::push_back( 
            &register_type::convertible,
            &register_type::construct,
            type_id<ExceptionType>());

    }

    static void * convertible(PyObject * py_obj)
    
    {
        if(1 != PyObject_IsInstance(py_obj, PyExc_Exception)) {
            return 0;
        }

        if(!PyObject_HasAttrString(py_obj, "cpp_error")) {
            return 0;
        }
        using namespace boost::python;

        object pyerr( handle<>( borrowed(py_obj) ) );
        object cpp_error = pyerr.attr("cpp_error");
        extract<ExceptionType> type_checker(cpp_error);
        if( !type_checker.check() ) {
            return 0;
        }
        return py_obj;

    }


     static void
        construct( PyObject* py_obj, boost::python::converter::rvalue_from_python_stage1_data* data){
        using namespace boost::python;
        typedef converter::rvalue_from_python_storage<ExceptionType> storage_t;
        
        object pyerr( handle<>( borrowed( py_obj ) ) );        
        object pimpl = pyerr.attr("cpp_error");
        
        storage_t* the_storage = reinterpret_cast<storage_t*>( data );
        void* memory_chunk = the_storage->storage.bytes;
        new (memory_chunk) ExceptionType( extract<ExceptionType>(pimpl) );
        
        data->convertible = memory_chunk;
    }

};

template<typename _ExceptionType, typename _BaseType>
typename exception_detail<_ExceptionType, _BaseType>::return_type exception_(const char * name, bool is_base = false)
{
    using namespace boost::python;
    typedef exception_detail<_ExceptionType, _BaseType> my_detail;

    scope current;
    object classobj = import("new").attr("classobj");
    dict d;
    object pybase = my_detail::pybase();

    object klass = classobj(name, boost::python::make_tuple(my_detail::pybase()), d);
    current.attr(name) = klass;

    void * ptr = reinterpret_cast<void*>(&exception_thrower<_ExceptionType>::do_throw);
    klass.attr("_thrower") = object( handle<>(
                    PyCObject_FromVoidPtr(ptr, 0) ) );
    
    scope s = klass;
    
    typename my_detail::return_type new_class("_cpp_error", init<const _ExceptionType &>());
    new_class.attr("py_err_class") = klass;
   
    exception_to_cpp<_ExceptionType>(); 

    return new_class;

}
#endif
